<script>
import CruResource from '@shell/components/CruResource';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import UnitInput from '@shell/components/form/UnitInput';
import { Banner } from '@components/Banner';
import Loading from '@shell/components/Loading';
import { COMPLIANCE, CONFIG_MAP } from '@shell/config/types';
import { mapGetters } from 'vuex';
import createEditView from '@shell/mixins/create-edit-view';
import { allHash } from '@shell/utils/promise';
import { Checkbox } from '@components/Form/Checkbox';
import { RadioGroup } from '@components/Form/Radio';
import { get } from '@shell/utils/object';
import { _VIEW, _CREATE } from '@shell/config/query-params';
import { isValidCron } from 'cron-validator';
import { fetchSpecsScheduledScanConfig } from '@shell/models/compliance.cattle.io.clusterscan';

const semver = require('semver');

export default {
  components: {
    CruResource, LabeledSelect, Banner, Loading, Checkbox, LabeledInput, RadioGroup, UnitInput
  },

  mixins: [createEditView],

  props: {
    value: {
      type:    Object,
      default: () => {
        return {};
      }
    },
    mode: {
      type:    String,
      default: 'create'
    }
  },

  async fetch() {
    // we need to force-fetch the resource fields, otherwise on page refresh
    // in the clusterscan edit/create views the "canBeScheduled" won't run properly
    await this.schema.fetchResourceFields();

    // Only initialize on create or if we don't have a spec object (added for resilience)
    if (this.realMode === _CREATE || !this.value.spec) {
      const includeScheduling = this.value.canBeScheduled();
      const spec = this.value.spec || {};

      spec.scanProfileName = null;
      if (includeScheduling) {
        spec.scoreWarning = 'pass';
        spec.scheduledScanConfig = { scanAlertRule: {}, retentionCount: 3 };
      }

      this.value.spec = spec;
    }

    if (!this.value.metadata.name) {
      this.value.metadata.generateName = 'scan-';
    }
    if (!this.value.spec.scheduledScanConfig) {
      this.value.spec['scheduledScanConfig'] = { scanAlertRule: {} };
    }
    if (!this.value.spec.scheduledScanConfig.scanAlertRule) {
      this.value.spec.scheduledScanConfig['scanAlertRule'] = { };
    }

    this.isScheduled = !!get(this.value, 'spec.scheduledScanConfig.cronSchedule');
    this.scheduledScanConfig = this.value.spec.scheduledScanConfig;
    this.scanAlertRule = this.value.spec.scheduledScanConfig.scanAlertRule;

    const hash = await allHash({
      profiles:               this.$store.dispatch('cluster/findAll', { type: COMPLIANCE.CLUSTER_SCAN_PROFILE }),
      benchmarks:             this.$store.dispatch('cluster/findAll', { type: COMPLIANCE.BENCHMARK }),
      // Ensure the clusterscan model has everything it needs
      hasScheduledScanConfig: fetchSpecsScheduledScanConfig(this.schema),
    });

    try {
      this.defaultConfigMap = await this.$store.dispatch('cluster/find', { type: CONFIG_MAP, id: 'compliance-operator-system/default-clusterscanprofiles' });
    } catch {}

    this.allProfiles = hash.profiles;
    const { scanProfileName } = this.value.spec;

    // if mode is _CREATE and scanProfileName is defined, this is a clone
    // check if the profile referred to in the original spec still exists
    if (scanProfileName && this.mode === _CREATE) {
      const proxyObj = this.allProfiles.filter((profile) => profile.id === scanProfileName)[0];

      if (!proxyObj) {
        this.value.spec['scanProfileName'] = '';
      }
    }
  },

  data() {
    return {
      allProfiles:         [],
      defaultConfigMap:    null,
      scheduledScanConfig: null,
      scanAlertRule:       null,
      hasAlertManager:     false,
      isScheduled:         null
    };
  },

  computed: {
    ...mapGetters({ currentCluster: 'currentCluster', t: 'i18n/t' }),

    canBeScheduled() {
      // check if scan was created and run with an older compliance install that doesn't support scheduling/alerting/warn state
      if (this.mode === _VIEW) {
        const warn = get(this.value, 'status.summary.warn');

        return !!warn || warn === 0;
      }

      return this.value.canBeScheduled();
    },

    validProfiles() {
      const profileNames = this.allProfiles.filter((profile) => {
        const benchmarkVersion = profile?.spec?.benchmarkVersion;
        const benchmark = this.$store.getters['cluster/byId'](COMPLIANCE.BENCHMARK, benchmarkVersion);

        return this.validateBenchmark(benchmark, this.currentCluster );
      }).map((profile) => {
        return { label: profile.id, value: profile.id };
      });

      return profileNames;
    },

    defaultProfile() {
      if (this.defaultConfigMap) {
        const profiles = this.defaultConfigMap.data || {}; // The config map might be empty, so ensure we have an object
        const provider = this.currentCluster.status.provider;

        let name = profiles[provider] || profiles.default;

        if (name?.includes(':')) {
          const pairs = name.split('\n');
          const clusterVersion = this.currentCluster.kubernetesVersion;

          pairs.forEach((pair) => {
            const version = (pair.match(/[<>=]+[-._a-zA-Z0-9]+/) || [])[0];

            try {
              if (semver.satisfies(clusterVersion, version)) {
                name = pair.replace(/[<>=]+[-._a-zA-Z0-9]+: /, '');
              }
            } catch (e) {
              // Ignore entries with invalid semver
            }
          });
        }
        if (name) {
          const profile = this.allProfiles.find((profile) => profile.id === name);
          const benchmarkVersion = profile?.spec?.benchmarkVersion;
          const benchmark = this.$store.getters['cluster/byId'](COMPLIANCE.BENCHMARK, benchmarkVersion);

          if (this.validateBenchmark(benchmark, this.currentCluster )) {
            return profile;
          }
        }

        // Just use the first one as the default - check the profiles we consider to be valid for this cluster first
        if (this.validProfiles.length > 0) {
          return this.allProfiles.find((profile) => profile.id === this.validProfiles[0].value) || null;
        }
      }

      // Can not find a default
      return null;
    },

    monitoringUrl() {
      return this.$router.resolve({
        name:   'c-cluster-monitoring',
        params: { cluster: this.$route.params.cluster }
      }).href;
    },

    validated() {
      if (this.isScheduled) {
        const schedule = get(this.value, 'spec.scheduledScanConfig.cronSchedule');

        if (!schedule) {
          return false;
        } else {
          // TODO - #13202: This is required due use of 2 libraries and 3 different libraries through the code.
          const predefined = [
            '@yearly',
            '@annually',
            '@monthly',
            '@weekly',
            '@daily',
            '@midnight',
            '@hourly'
          ];
          const isPredefined = predefined.includes(schedule);

          return (isPredefined || isValidCron(schedule)) && !!this.value.spec.scanProfileName;
        }
      }

      return !!this.value.spec.scanProfileName;
    }

  },

  watch: {
    defaultProfile(neu) {
      if (neu && !this.value.spec.scanProfileName) {
        const benchmarkVersion = neu?.spec?.benchmarkVersion;
        const benchmark = this.$store.getters['cluster/byId'](COMPLIANCE.BENCHMARK, benchmarkVersion);

        if (!this.validateBenchmark(benchmark, this.currentCluster)) {
          return;
        }
        this.value.spec.scanProfileName = neu?.id;
      }
    },
  },

  methods: {
    validateBenchmark(benchmark, currentCluster) {
      if (!!benchmark?.spec?.clusterProvider) {
        if ( benchmark?.spec?.clusterProvider !== currentCluster.status.provider) {
          return false;
        }
      }

      try {
        const clusterVersion = currentCluster.kubernetesVersionDisplay;

        if (benchmark?.spec?.minKubernetesVersion) {
          if (semver.gt(benchmark?.spec?.minKubernetesVersion, clusterVersion)) {
            return false;
          }
        }
        if (benchmark?.spec?.maxKubernetesVersion) {
          if (semver.gt(clusterVersion, benchmark?.spec?.maxKubernetesVersion)) {
            return false;
          }
        }
      } catch (e) {
        // Ignore error if something is invalid semver
      }

      return true;
    },

    saveScan(cb) {
      if (!this.value.isScheduled || !this.isScheduled) {
        delete this.value.spec.scheduledScanConfig;
      }
      this.save(cb);
    },
  }
};
</script>

<template>
  <Loading v-if="$fetchState.pending" />

  <CruResource
    v-else
    :validation-passed="validated"
    :done-route="doneRoute"
    :resource="value"
    :mode="mode"
    :errors="errors"
    @finish="saveScan"
    @error="e=>errors = e"
  >
    <Banner
      v-if="!validProfiles.length"
      color="warning"
      :label="t('compliance.noProfiles')"
    />

    <div
      v-else
      class="row mb-20"
    >
      <div class="col span-6">
        <LabeledSelect
          v-model:value="value.spec.scanProfileName"
          :mode="mode"
          :label="t('compliance.profile')"
          :options="validProfiles"
        />
      </div>
      <div
        v-if="canBeScheduled"
        class="col span-6"
      >
        <span>{{ t('compliance.scoreWarning.label') }}</span> <i
          v-clean-tooltip="t('compliance.scoreWarning.protip')"
          class="icon icon-info"
        />
        <RadioGroup
          v-model:value="value.spec.scoreWarning"
          :mode="mode"
          name="scoreWarning"
          :options="['pass', 'fail']"
          :labels="[t('compliance.scan.pass'), t('compliance.scan.fail')]"
        />
      </div>
    </div>
    <template v-if="canBeScheduled">
      <h3>{{ t('compliance.scheduling.title') }}</h3>
      <div class="row mb-20">
        <div class="col">
          <RadioGroup
            v-model:value="isScheduled"
            :mode="mode"
            name="scheduling"
            :options="[ {value: false, label: t('compliance.scheduling.disable')}, {value: true, label: t('compliance.scheduling.enable')}]"
          />
        </div>
      </div>
      <template v-if="isScheduled">
        <div class="row mb-20">
          <div class="col span-6">
            <LabeledInput
              v-model:value="scheduledScanConfig.cronSchedule"
              required
              :mode="mode"
              :label="t('compliance.cronSchedule.label')"
              :placeholder="t('compliance.cronSchedule.placeholder')"
              type="cron"
            />
          </div>
          <div class="col span-6">
            <UnitInput
              v-model:value="scheduledScanConfig.retentionCount"
              :suffix="t('compliance.reports')"
              type="number"
              :mode="mode"
              :label="t('compliance.retention')"
            />
          </div>
        </div>
        <h3 class="mt-20">
          {{ t('compliance.alerting') }}
        </h3>
        <div class="row mb-20">
          <div class="col span-12">
            <Banner
              v-if="scanAlertRule.alertOnFailure || scanAlertRule.alertOnComplete"
              class="mt-0"
              :color="hasAlertManager ? 'info' : 'warning'"
            >
              <span v-clean-html="t('compliance.alertNeeded', {link: monitoringUrl}, true)" />
            </banner>
            <Checkbox
              v-model:value="scanAlertRule.alertOnComplete"
              :mode="mode"
              :label="t('compliance.alertOnComplete')"
            />
            <Checkbox
              v-model:value="scanAlertRule.alertOnFailure"
              :mode="mode"
              :label="t('compliance.alertOnFailure')"
            />
          </div>
        </div>
      </template>
    </template>
  </CruResource>
</template>
